{
 "cells": [
  {
   "cell_type": "code",
   "id": "initial_id",
   "metadata": {
    "collapsed": true,
    "jupyter": {
     "is_executing": true
    }
   },
   "source": "",
   "outputs": [],
   "execution_count": null
  },
  {
   "metadata": {},
   "cell_type": "code",
   "outputs": [],
   "execution_count": null,
   "source": [
    "import numpy as np\n",
    "\n",
    "\n",
    "class RNN:\n",
    "    def __init__(self, input_size, hidden_size, output_size):\n",
    "        \"\"\"初始化RNN模型\"\"\"\n",
    "        self.Wxh = np.random.randn(input_size, hidden_size) * 0.01  # 输入到隐藏层权重\n",
    "        self.Whh = (\n",
    "            np.random.randn(hidden_size, hidden_size) * 0.01\n",
    "        )  # 隐藏层到隐藏层权重\n",
    "        self.Why = (\n",
    "            np.random.randn(hidden_size, output_size) * 0.01\n",
    "        )  # 隐藏层到输出层权重\n",
    "        self.bh = np.zeros((1, hidden_size))  # 隐藏层偏置\n",
    "        self.by = np.zeros((1, output_size))  # 输出层偏置\n",
    "\n",
    "    def forward(self, xt, ht_1):\n",
    "        \"\"\"前向传播\"\"\"\n",
    "        ht = np.tanh(np.dot(xt, self.Wxh) + np.dot(ht_1, self.Whh) + self.bh)\n",
    "        yt = np.dot(ht, self.Why) + self.by\n",
    "        return ht, yt\n",
    "\n",
    "    def backward(self, x, y, h, learning_rate=0.01):\n",
    "        \"\"\"反向传播\"\"\"\n",
    "        dWxh = np.zeros_like(self.Wxh)\n",
    "        dWhh = np.zeros_like(self.Whh)\n",
    "        dWhy = np.zeros_like(self.Why)\n",
    "        dbh = np.zeros_like(self.bh)\n",
    "        dby = np.zeros_like(self.by)\n",
    "\n",
    "        dh_next = np.zeros_like(h[0])\n",
    "        loss = 0\n",
    "\n",
    "        for t in reversed(range(len(x))):\n",
    "            yt = np.dot(h[t], self.Why) + self.by\n",
    "            dy = yt - y[t]\n",
    "            loss += np.mean(dy**2) / 2\n",
    "\n",
    "            dWhy += np.dot(h[t].T, dy)\n",
    "            dby += dy\n",
    "\n",
    "            dh = np.dot(dy, self.Why.T) + dh_next\n",
    "            dtanh = 1 - h[t] ** 2\n",
    "            dh_raw = dh * dtanh\n",
    "\n",
    "            dWxh += np.dot(x[t].T, dh_raw)\n",
    "            dbh += dh_raw\n",
    "            dWhh += np.dot(h[t - 1].T, dh_raw)\n",
    "\n",
    "            dh_next = np.dot(dh_raw, self.Whh.T)\n",
    "\n",
    "        for dparam in [dWxh, dWhh, dWhy, dbh, dby]:\n",
    "            np.clip(dparam, -5, 5, out=dparam)\n",
    "\n",
    "        self.Wxh -= learning_rate * dWxh\n",
    "        self.Whh -= learning_rate * dWhh\n",
    "        self.Why -= learning_rate * dWhy\n",
    "        self.bh -= learning_rate * dbh\n",
    "        self.by -= learning_rate * dby\n",
    "\n",
    "        return loss\n",
    "\n",
    "    def train(self, x_train, y_train, epochs=100, learning_rate=0.01, batch_size=32):\n",
    "        \"\"\"_summary_\n",
    "        训练RNN模型\n",
    "        Args:\n",
    "            x_train (list): 训练输入序列，形状为 [(num_samples, T, input_size)]\n",
    "            y_train (list): 训练目标序列，形状为 [(num_samples, T, output_size)]\n",
    "            epochs (int): 训练轮数\n",
    "            learning_rate (float): 学习率\n",
    "            batch_size (int): 批次大小\n",
    "        Returns:\n",
    "            list: 每轮的平均损失\n",
    "        \"\"\"\n",
    "        losses = []\n",
    "        num_samples = len(x_train)\n",
    "\n",
    "        for epoch in range(epochs):\n",
    "            epoch_loss = 0\n",
    "            # 随机打乱数据\n",
    "            indices = np.random.permutation(num_samples)\n",
    "\n",
    "            for i in range(0, num_samples, batch_size):\n",
    "                batch_indices = indices[i : min(i + batch_size, num_samples)]\n",
    "\n",
    "                for idx in batch_indices:\n",
    "                    x = x_train[idx]  # (T, input_size)\n",
    "                    y = y_train[idx]  # (T, output_size)\n",
    "\n",
    "                    # 初始化隐藏状态\n",
    "                    h = [np.zeros((1, self.Whh.shape[0]))]\n",
    "\n",
    "                    # 前向传播\n",
    "                    for t in range(len(x)):\n",
    "                        ht, yt = self.forward(x[t], h[-1])\n",
    "                        h.append(ht)\n",
    "\n",
    "                    # 反向传播\n",
    "                    loss = self.backward(x, y, h, learning_rate)\n",
    "                    epoch_loss += loss\n",
    "\n",
    "                # 平均批次损失\n",
    "                epoch_loss /= len(batch_indices)\n",
    "\n",
    "            losses.append(epoch_loss)\n",
    "            if (epoch + 1) % 10 == 0:\n",
    "                print(f\"Epoch {epoch + 1}/{epochs}, Loss: {epoch_loss:.6f}\")\n",
    "\n",
    "        return losses\n",
    "\n",
    "    def evaluate(self, x_test, y_test):\n",
    "        \"\"\"_summary_\n",
    "        评估RNN模型\n",
    "        Args:\n",
    "            x_test (list): 测试输入序列，形状为 [(num_samples, T, input_size)]\n",
    "            y_test (list): 测试目标序列，形状为 [(num_samples, T, output_size)]\n",
    "        Returns:\n",
    "            float: 测试集上的平均MSE损失\n",
    "        \"\"\"\n",
    "        total_loss = 0\n",
    "        num_samples = len(x_test)\n",
    "\n",
    "        for i in range(num_samples):\n",
    "            x = x_test[i]  # (T, input_size)\n",
    "            y = y_test[i]  # (T, output_size)\n",
    "\n",
    "            # 初始化隐藏状态\n",
    "            h = np.zeros((1, self.Whh.shape[0]))\n",
    "            loss = 0\n",
    "\n",
    "            # 前向传播\n",
    "            for t in range(len(x)):\n",
    "                h, yt = self.forward(x[t], h)\n",
    "                dy = yt - y[t]\n",
    "                loss += np.mean(dy**2) / 2\n",
    "\n",
    "            total_loss += loss\n",
    "\n",
    "        return total_loss / num_samples"
   ],
   "id": "428f704930a0bb4a"
  },
  {
   "metadata": {},
   "cell_type": "code",
   "outputs": [],
   "execution_count": null,
   "source": [
    "def generate_sine_wave(num_samples, T, input_size, output_size):\n",
    "    x_data = []\n",
    "    y_data = []\n",
    "    for _ in range(num_samples):\n",
    "        t = np.linspace(0, 10, T)\n",
    "        x = np.sin(t).reshape(T, 1)  # 输入是正弦值\n",
    "        y = np.sin(t + 0.1).reshape(T, 1)  # 输出是稍后的正弦值\n",
    "        x_data.append(x)\n",
    "        y_data.append(y)\n",
    "    return x_data, y_data\n",
    "\n",
    "\n",
    "# 参数\n",
    "input_size = 1\n",
    "hidden_size = 32\n",
    "output_size = 1\n",
    "T = 50\n",
    "num_train = 100\n",
    "num_test = 20\n",
    "\n",
    "# 生成数据\n",
    "x_train, y_train = generate_sine_wave(num_train, T, input_size, output_size)\n",
    "x_test, y_test = generate_sine_wave(num_test, T, input_size, output_size)\n",
    "\n",
    "# 初始化RNN\n",
    "rnn = RNN(input_size, hidden_size, output_size)\n",
    "\n",
    "# 训练\n",
    "losses = rnn.train(x_train, y_train, epochs=50, learning_rate=0.001, batch_size=32)\n",
    "\n",
    "# 评估\n",
    "test_loss = rnn.evaluate(x_test, y_test)\n",
    "print(f\"Test Loss: {test_loss:.6f}\")\n",
    "\n",
    "# 可视化训练损失\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "plt.plot(losses)\n",
    "plt.xlabel(\"Epoch\")\n",
    "plt.ylabel(\"Loss\")\n",
    "plt.title(\"Training Loss\")\n",
    "plt.show()"
   ],
   "id": "ab8004af0d9a7161"
  },
  {
   "metadata": {},
   "cell_type": "code",
   "outputs": [],
   "execution_count": null,
   "source": "",
   "id": "d9b4597cd7fdb83b"
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
